﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.Image
{
	/// <summary>
	/// Class for implementation of image conversion operations.
	/// </summary>
	public class ImageConversion
	{
		#region - To grayscale. -
		public static void ConvertToGrayscale(Bitmap image)
		{
			try
			{
				if (image != null)
				{
					var rect = new Rectangle(0, 0, image.Size.Width, image.Size.Height);
					BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

					int bytesSize = image.Height * bmpData.Stride;
					int alignByte = bmpData.Stride - image.Width * 3;
					var bytes = new byte[bytesSize];
					System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

					int temp,
						width = image.Width;
					for (int i = 0; i < bytes.Length; )
					{
						for (int j = 0; j < width; ++j, i += 3)
						{
							temp = (int)(bytes[i] * Const.GRAYSCALE_B_COEFF
										+ bytes[i + 1] * Const.GRAYSCALE_G_COEFF
										+ bytes[i + 2] * Const.GRAYSCALE_R_COEFF);
							bytes[i] = bytes[i + 1] = bytes[i + 2] = (byte)temp;
						}
						i += alignByte;
					}

					System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, bytesSize);
					image.UnlockBits(bmpData);
				}
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.ToString());
			}
		}

		public static byte[,] ConvertToGrayscaleTable(Bitmap image)
		{
			var res = new byte[image.Height,image.Width];

			try
			{
				int width = image.Width, height = image.Height;

				var rect = new Rectangle(0, 0, image.Size.Width, image.Size.Height);
				BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

				int bytesSize = image.Height * bmpData.Stride;
				int alignByte = bmpData.Stride - image.Width * 3;
				var bytes = new byte[bytesSize];
				System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

				int count = 0;
				for (int i = 0; i < height; ++i)
				{
					for (int j = 0; j < width; ++j, count += 3)
					{
						var temp = (byte)(bytes[count] * Const.GRAYSCALE_B_COEFF
									+ bytes[count + 1] * Const.GRAYSCALE_G_COEFF
									+ bytes[count + 2] * Const.GRAYSCALE_R_COEFF);
						res[i, j] = temp;
					}
					count += alignByte;
				}

				image.UnlockBits(bmpData);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.ToString());
			}

			return res;
		}

		public static float[,] ConvertToGrayscaleTableFloat(Bitmap image)
		{
			var res = new float[image.Height, image.Width];

			try
			{
				int width = image.Width, height = image.Height;

				var rect = new Rectangle(0, 0, image.Size.Width, image.Size.Height);
				BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

				int bytesSize = image.Height * bmpData.Stride;
				int alignByte = bmpData.Stride - image.Width * 3;
				var bytes = new byte[bytesSize];
				System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

				int count = 0;
				for (int i = 0; i < height; ++i)
				{
					for (int j = 0; j < width; ++j, count += 3)
					{
						var temp = (bytes[count] * Const.GRAYSCALE_B_COEFF
									+ bytes[count + 1] * Const.GRAYSCALE_G_COEFF
									+ bytes[count + 2] * Const.GRAYSCALE_R_COEFF);
						res[i, j] = temp;
					}
					count += alignByte;
				}

				image.UnlockBits(bmpData);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.ToString());
			}

			return res;
		}

		public static bool IsGrayscale(Bitmap image)
		{
			var rect = new Rectangle(0, 0, image.Size.Width, image.Size.Height);
			BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

			int bytesSize = image.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			bool res = true;
			int width = bmpData.Width * 3,
				alignByte = bmpData.Stride - width;
			for (int i = 0; i < bytesSize; )
			{
				int limit = i + width;
				for (int j = i; j < limit; j += 3)
				{
					if ((bytes[j] != bytes[j + 1]) || (bytes[j] != bytes[j + 2]))
					{
						res = false;
						break;
					}
				}
				if (!res) break;
				i = limit + alignByte;
			}

			image.UnlockBits(bmpData);
			return res;
		}
		#endregion

		#region - To HSV color space. -
		public static Bitmap GetHue(Bitmap image)
		{
			var result = new Bitmap(image);

			var rect = new Rectangle(0, 0, result.Size.Width, result.Size.Height);
			BitmapData bmpData = result.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			int bytesSize = result.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			int max, min, count = 0,
				alignByte = bmpData.Stride - bmpData.Width * 3;
			int height = image.Height,
				width = image.Width;
			float scale = 255.0f / 360.0f;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j, count += 3)
				{
					int b = bytes[count], g = bytes[count + 1], r = bytes[count + 2];

					//max = Math.Max(b, g);
					//max = Math.Max(max, r);
					max = b > g ? b : g;
					if (r > max) { max = r; }

					//min = Math.Min(b, g);
					//min = Math.Min(min, r);
					min = b < g ? b : g;
					if (r < min) { min = r; }

					if (max != min)
					{
						int res = 0;

						float tempDiv = 60.0f / (max - min);
						if (max == r)
						{
							res = (int)(((g - b) * tempDiv + 360.0) % 360);
						}
						else if (max == g)
						{
							res = (int)(((b - r) * tempDiv + 120.0) % 360);
						}
						else
						{
							res = (int)(((r - g) * tempDiv + 240.0) % 360);
						}

						bytes[count] = bytes[count + 1] = bytes[count + 2] = (byte)(res * scale);
					}
					else
					{
						bytes[count] = bytes[count + 1] = bytes[count + 2] = 0;
					}
				}

				count += alignByte;
			}

			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, bytesSize);
			result.UnlockBits(bmpData);

			return result;
		}

		public static byte[,] GetHueTable(Bitmap image)
		{
			int height = image.Height,
				width = image.Width;
			var res = new byte[height, width];

			var rect = new Rectangle(0, 0, image.Size.Width, image.Size.Height);
			BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
			int bytesSize = image.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			int max, min, count = 0,
				alignByte = bmpData.Stride - bmpData.Width * 3;
			float scale = 255.0f / 360.0f;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j, count += 3)
				{
					int b = bytes[count], g = bytes[count + 1], r = bytes[count + 2];

					max = b > g ? b : g;
					if (r > max) { max = r; }

					min = b < g ? b : g;
					if (r < min) { min = r; }

					if (max != min)
					{
						int resHue = 0;

						float tempDiv = 60.0f / (max - min);
						if (max == r)
						{
							resHue = (int)(((g - b) * tempDiv + 360.0) % 360);
						}
						else if (max == g)
						{
							resHue = (int)(((b - r) * tempDiv + 120.0) % 360);
						}
						else
						{
							resHue = (int)(((r - g) * tempDiv + 240.0) % 360);
						}

						res[i,j] = (byte)(resHue * scale);
					}
					else
					{
						res[i,j] = 0;
					}
				}

				count += alignByte;
			}

			image.UnlockBits(bmpData);

			return res;
		}

		public static Bitmap GetSaturation(Bitmap image)
		{
			var result = new Bitmap(image);

			var rect = new Rectangle(0, 0, result.Size.Width, result.Size.Height);
			BitmapData bmpData = result.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			int bytesSize = result.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			int max, min, count = 0,
				alignByte = bmpData.Stride - bmpData.Width * 3;
			int height = image.Height,
				width = image.Width;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j, count += 3)
				{
					int b = bytes[count], g = bytes[count + 1], r = bytes[count + 2];

					max = b > g ? b : g;
					if (r > max) { max = r; }

					min = b < g ? b : g;
					if (r < min) { min = r; }

					byte temp = (max != 0)
								?
								(byte)(255 * (max - min) / (float)max)
								:
								(byte)0;
					bytes[count] = bytes[count + 1] = bytes[count + 2] = temp;
				}
				count += alignByte;
			}

			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, bytesSize);
			result.UnlockBits(bmpData);

			return result;
		}

		public static byte[,] GetSaturationTable(Bitmap image)
		{
			int height = image.Height,
				width = image.Width;
			var res = new byte[ height, width ];

			var rect = new Rectangle(0, 0, image.Size.Width, image.Size.Height);
			BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
			int bytesSize = image.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			int max, min, count = 0,
				alignByte = bmpData.Stride - bmpData.Width * 3;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j, count += 3)
				{
					int b = bytes[count], g = bytes[count + 1], r = bytes[count + 2];

					max = b > g ? b : g;
					if (r > max) { max = r; }

					min = b < g ? b : g;
					if (r < min) { min = r; }

					byte temp = (max != 0)
								?
								(byte)(255 * (max - min) / (float)max)
								:
								(byte)0;
					res[i,j] = temp;
				}
				count += alignByte;
			}

			image.UnlockBits(bmpData);

			return res;
		}
		
		public static Bitmap GetValue(Bitmap image)
		{
			var result = new Bitmap(image);

			var rect = new Rectangle(0, 0, result.Size.Width, result.Size.Height);
			BitmapData bmpData = result.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			int bytesSize = result.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			for (int i = 0; i < bytes.Length; i += 3)
			{
				int temp = Math.Max(bytes[i], bytes[i + 1]);
				temp = Math.Max(temp, bytes[i + 2]);
				bytes[i] = bytes[i + 1] = bytes[i + 2] = (byte)temp;
			}

			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, bytesSize);
			result.UnlockBits(bmpData);

			return result;
		}
		#endregion

		#region - To table of values. -
		/// <summary>
		/// [atomic]
		/// 
		/// Convert selected [channel] from given image into the 2d array.
		/// </summary>
		/// <param name="image">24-bit (3-channels) image.</param>
		/// <param name="channel">ID of the channel to be written into the output array.</param>
		/// <returns>2d array representation for the given [channel]</returns>
		public static byte[,] ConvertTo2DTable(Bitmap image, int channel)
		{
			int width = image.Width, height = image.Height;
			var res = new byte[height, width];

			var rect = new Rectangle(0, 0, image.Size.Width, image.Size.Height);
			BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int bytesSize = bmpData.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			int stride = bmpData.Stride;
			int alignByte = stride - width * 3, count = channel;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j, count += 3)
				{
					res[i, j] = bytes[count];
				}
				count += alignByte;
			}

			image.UnlockBits(bmpData);
			return res;
		}

		/// <summary>
		/// [atomic]
		/// 
		/// Convert selected [channel] from given image into the 2d array.
		/// </summary>
		/// <param name="image">24-bit (3-channels) image.</param>
		/// <param name="channel">ID of the channel to be written into the output array.</param>
		/// <returns>2d array representation for the given [channel]</returns>
		public static float[,] ConvertTo2DTableFloat(Bitmap image, int channel)
		{
			int width = image.Width, height = image.Height;
			var res = new float[height, width];

			var rect = new Rectangle(0, 0, image.Size.Width, image.Size.Height);
			BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int bytesSize = bmpData.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			int stride = bmpData.Stride;
			int alignByte = stride - width * 3, count = channel;
			for (int i = 0; i < height; ++i)
			{
				count = i*stride + channel;
				for (int j = 0; j < width; ++j, count += 3)
				{
					res[i, j] = bytes[count];
				}
				//count += alignByte;
			}

			image.UnlockBits(bmpData);
			return res;
		}
		#endregion

		#region - To bitmap. -
		/// <summary>
		/// [molecule]
		/// 
		/// Creates a bitmap from the given table of floats.
		/// </summary>
		/// <param name="table">Source table.</param>
		/// <returns>Resulting bitmap.</returns>
		public static Bitmap CreateBmpFromGrayscaleTable(float[,] table)
		{
			int width = table.GetLength(1), height = table.GetLength(0);
			var res = new Bitmap(width, height);

			var rect = new Rectangle(0, 0, res.Size.Width, res.Size.Height);
			BitmapData bmpData = res.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int bytesSize = bmpData.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			int alignByte = bmpData.Stride - width*3;
			int count = 0;
			for (int i=0; i<height; ++i)
			{
				for (int j=0; j<width; ++j, count += 3)
				{
					bytes[count] = bytes[count + 1] = bytes[count + 2] = (byte)table [i,j];
				}
				count += alignByte;
			}

			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, bytesSize);
			res.UnlockBits(bmpData);

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Creates a bitmap from the given table of floats with a preliminary normalization of table values.
		/// </summary>
		/// <param name="table">Source table.</param>
		/// <returns>Resulting bitmap.</returns>
		public static Bitmap CreateBmpFromGrayscaleTableNorm(float[,] table)
		{
			var tempT = (float[,])table.Clone();
			MatrixMath.NormalizeSelf(ref tempT);
			tempT = MatrixMath.Mul(tempT, Const.GRAYLEVEL_MAX);
			return CreateBmpFromGrayscaleTable(tempT);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Creates a bitmap from the given table of bytes.
		/// </summary>
		/// <param name="table">Source table.</param>
		/// <returns>Resulting bitmap.</returns>
		public static Bitmap CreateBmpFromGrayscaleTable(byte[,] table)
		{
			int width = table.GetLength(1), height = table.GetLength(0);
			var res = new Bitmap(width, height);

			var rect = new Rectangle(0, 0, res.Size.Width, res.Size.Height);
			BitmapData bmpData = res.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int bytesSize = bmpData.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			int alignByte = bmpData.Stride - width * 3;
			int count = 0;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j, count += 3)
				{
					bytes[count] = bytes[count + 1] = bytes[count + 2] = table[i, j];
				}
				count += alignByte;
			}

			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, bytesSize);
			res.UnlockBits(bmpData);

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Creates a bitmap from the given color planes, which should follow the (R,G,B) order.
		/// </summary>
		/// <param name="planes">Source color planes.</param>
		/// <returns>Resulting bitmap.</returns>
		public static Bitmap CreateBmpFromRgbTables(List<byte[,]> planes)
		{
			int width = planes[0].GetLength(1), height = planes[0].GetLength(0);
			var res = new Bitmap(width, height);

			var rect = new Rectangle(0, 0, res.Size.Width, res.Size.Height);
			BitmapData bmpData = res.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int bytesSize = bmpData.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			int alignByte = bmpData.Stride - width * 3;
			int count = 0;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j, count += 3)
				{
					bytes[count] = planes[2][i, j];		// blue.
					bytes[count+1] = planes[1][i, j];	// green.
					bytes[count+2] = planes[0][i, j];	// red.
				}
				count += alignByte;
			}

			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, bytesSize);
			res.UnlockBits(bmpData);

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Saves given list of tables as bitmap files using specified filenames prefixes and giving additional ordinal number suffix to each file.
		/// </summary>
		/// <param name="tables">Source list of tables.</param>
		/// <param name="filePrefix">Filename prefix.</param>
		public static void SaveTablesAsBitmaps (List<float[,]> tables, string filePrefix)
		{
			int count = 0;
			foreach (var table in tables)
			{
				var bmp = CreateBmpFromGrayscaleTableNorm(table);
				string filename = string.Format("{0}_{1}.png", filePrefix, count);
				bmp.Save(filename);
				count++;
			}
		}

		#endregion

		#region - Resize. -
		/// <summary>
		/// [molecule]
		/// 
		/// Changes image size to the desired one.
		/// </summary>
		/// <param name="src">Source image.</param>
		/// <param name="width">Desired width.</param>
		/// <param name="height">Desired height.</param>
		/// <returns>Resized image.</returns>
		public static Bitmap Resize (Bitmap src, int width, int height)
		{
			var bmp = new Bitmap(width, height);
			Graphics g = Graphics.FromImage(bmp);
			g.InterpolationMode = InterpolationMode.HighQualityBicubic;

			g.DrawImage(src, 0, 0, width, height);
			g.Dispose();
			return bmp;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs resize of the input image so that the largest dimension would not
		/// exceed the [maxSide]. If input image already meets this requirement then nothing happens.
		/// </summary>
		/// <param name="src">Input image.</param>
		/// <param name="maxSide">Maximal dimension size allowed.</param>
		/// <returns>Resized image.</returns>
		public static Bitmap ResizeReduceOnly (Bitmap src, int maxSide)
		{
			Bitmap res;
			if (src.Size.Width > maxSide || src.Size.Height > maxSide)
			{	// reduce image size.
				float factor = src.Size.Width > src.Size.Height
								? src.Size.Width / (float)maxSide
								: src.Size.Height / (float)maxSide;
				var width = (int)(src.Size.Width / factor);
				var height = (int)(src.Size.Height / factor);
				res = new Bitmap(src, width, height);
			}
			else
			{	// do not do anything.
				res = src;
			}
			return res;
		}
		#endregion

		#region - LUT and color scale operations. -
		/// <summary>
		/// [molecule]
		/// 
		/// Calculates LUT for the linear transform using given transform parameters.
		/// </summary>
		/// <param name="k">Transform angle.</param>
		/// <param name="b">Transform offset.</param>
		/// <returns>LUT.</returns>
		public static byte[] CalculateLutLinear (float k, float b)
		{
			var lut = new byte[Const.GRAYLEVELS_COUNT];
			for (int i = 0; i < Const.GRAYLEVELS_COUNT; i++)
			{
				var temp = i * k + b;
				if (temp < 0) temp = 0;
				if (temp > Const.GRAYLEVEL_MAX) temp = Const.GRAYLEVEL_MAX;
				lut[i] = (byte)temp;
			}
			return lut;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Applies given look-up table to the given image.
		/// Note: The resulting image is grayscale.
		/// </summary>
		/// <param name="src">Source bitmap.</param>
		/// <param name="lut">LUT for transformation.</param>
		/// <returns>Modified bitmap.</returns>
		public static Bitmap ApplyLut (Bitmap src, byte[] lut)
		{
			var imgData = ConvertToGrayscaleTable(src);

			//
			// apply LUT.
			int height = imgData.GetLength(0), width = imgData.GetLength(1);
			for (int i0 = 0; i0 < height; i0++)
			{
				for (int i1 = 0; i1 < width; i1++)
				{
					var x = imgData[i0, i1];
					imgData[i0, i1] = lut[x];
				}
			}

			//
			// apply changes to bitmap.
			var res = CreateBmpFromGrayscaleTable(imgData);
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Applies given color scale to the given image.
		/// </summary>
		/// <param name="img">Input image.</param>
		/// <param name="cScale">Color scale.</param>
		/// <returns>Resulting bitmap.</returns>
		public static Bitmap ApplyColorScale(Bitmap img, Color[] cScale)
		{
			var res = new Bitmap(img.Width, img.Height);


			var rect = new Rectangle(0, 0, res.Size.Width, res.Size.Height);
			BitmapData bmpData = res.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			//
			// prepare data and variables.
			int bytesSize = bmpData.Height * bmpData.Stride;
			var bytes = new byte[bytesSize];
			System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, bytesSize);

			var grayTable = ConvertToGrayscaleTable(img);
			int height = res.Height, width = res.Width;
			int alignByte = bmpData.Stride - width * 3;
			int count = 0;

			//
			// apply color scale.
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j, count += 3)
				{
					var idx = grayTable[i, j];
					var c = cScale[idx];

					bytes[count] = c.B;
					bytes[count + 1] = c.G;
					bytes[count + 2] = c.R;
				}
				count += alignByte;
			}

			//
			// write the result.
			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, bytesSize);
			res.UnlockBits(bmpData);

			return res;
		}
		#endregion

		#region - Random image creation. -
		public enum ImageObjects
		{
			HorizontalLines,
			VerticalLines,
			RandomLines,
			Circles,
			RandomObjects
		} ;

		/// <summary>
		/// [molecule]
		/// 
		/// Returns new image with the prescribed size filled with the given objects.
		/// The objects' color is selected at random.
		/// </summary>
		/// <param name="size">Image size.</param>
		/// <param name="objType">Image objects type.</param>
		/// <param name="objCount">Number of objects.</param>
		/// <returns>Random image.</returns>
		public static Bitmap CreateRandomImage (Size size, ImageObjects objType, int objCount)
		{
			switch (objType)
			{
				case ImageObjects.HorizontalLines:
					return CreateRandomImageHLines(size, objCount);
				case ImageObjects.VerticalLines:
					return CreateRandomImageVLines(size, objCount);
				case ImageObjects.RandomLines:
					return CreateRandomImageRLines(size, objCount);
				case ImageObjects.Circles:
					return CreateRandomImageCircles(size, objCount);
				default:
					throw new Exception(string.Format("Unrecognized image object type ({0})", objType.ToString()));
			}
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Creates random image filled with the horizontal lines.
		/// </summary>
		/// <param name="size">Image size.</param>
		/// <param name="objCount">Image objects count.</param>
		/// <returns>Random image.</returns>
		public static Bitmap CreateRandomImageHLines(Size size, int objCount)
		{
			var res = new Bitmap(size.Width, size.Height);

			int height = size.Height, width = size.Width;
			var graph = Graphics.FromImage(res);
			for (int i = 0; i < objCount; i++)
			{
				int r = ContextRandom.Next(256),
				    g = ContextRandom.Next(256),
				    b = ContextRandom.Next(256);
				var pen = new Pen(Color.FromArgb(r, g, b), ContextRandom.Next(10));

				var x1 = ContextRandom.Next(width);
				var x2 = ContextRandom.Next(width);
				var y = ContextRandom.Next(height);
				graph.DrawLine(pen, x1, y, x2, y);
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Creates random image filled with the vertical lines.
		/// </summary>
		/// <param name="size">Image size.</param>
		/// <param name="objCount">Image objects count.</param>
		/// <returns>Random image.</returns>
		public static Bitmap CreateRandomImageVLines(Size size, int objCount)
		{
			var res = new Bitmap(size.Width, size.Height);

			int height = size.Height, width = size.Width;
			var graph = Graphics.FromImage(res);
			for (int i = 0; i < objCount; i++)
			{
				int r = ContextRandom.Next(256),
					g = ContextRandom.Next(256),
					b = ContextRandom.Next(256);
				var pen = new Pen(Color.FromArgb(r, g, b), ContextRandom.Next(10));

				var x = ContextRandom.Next(width);
				var y1 = ContextRandom.Next(height);
				var y2 = ContextRandom.Next(height);
				graph.DrawLine(pen, x, y1, x, y2);
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Creates random image filled with the randomly oriented lines.
		/// </summary>
		/// <param name="size">Image size.</param>
		/// <param name="objCount">Image objects count.</param>
		/// <returns>Random image.</returns>
		public static Bitmap CreateRandomImageRLines(Size size, int objCount)
		{
			var res = new Bitmap(size.Width, size.Height);

			int height = size.Height, width = size.Width;
			var graph = Graphics.FromImage(res);
			for (int i = 0; i < objCount; i++)
			{
				int r = ContextRandom.Next(256),
					g = ContextRandom.Next(256),
					b = ContextRandom.Next(256);
				var pen = new Pen(Color.FromArgb(r, g, b), ContextRandom.Next(10));

				var x1 = ContextRandom.Next(width);
				var x2 = ContextRandom.Next(width);
				var y1 = ContextRandom.Next(height);
				var y2 = ContextRandom.Next(height);
				graph.DrawLine(pen, x1, y1, x2, y2);
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Creates random image filled with the randomly placed ellipses.
		/// </summary>
		/// <param name="size">Image size.</param>
		/// <param name="objCount">Image objects count.</param>
		/// <returns>Random image.</returns>
		public static Bitmap CreateRandomImageCircles(Size size, int objCount)
		{
			var res = new Bitmap(size.Width, size.Height);

			int height = size.Height, width = size.Width;
			var graph = Graphics.FromImage(res);
			for (int i = 0; i < objCount; i++)
			{
				int r = ContextRandom.Next(256),
					g = ContextRandom.Next(256),
					b = ContextRandom.Next(256);
				var pen = new Pen(Color.FromArgb(r, g, b), ContextRandom.Next(10));

				var x1 = ContextRandom.Next(width);
				var y1 = ContextRandom.Next(height);
				var r1 = ContextRandom.Next(width) / 2;
				var r2 = ContextRandom.Next(height) / 2;
				graph.DrawEllipse(pen, x1-r1, y1-r2, x1+r1, y1+r2);
			}

			return res;
		}
		#endregion
	}
}
